package cn.yx.common.core.util.io;

import cn.hutool.core.util.ArrayUtil;
import cn.yx.common.core.constant.FileConstant;
import cn.yx.common.core.constant.SeparatorConstant;
import cn.yx.common.core.constant.message.io.FileUtilMessageKey;
import cn.yx.common.core.enums.OrderByTypeEnum;
import cn.yx.common.core.enums.date.DatePatternEnum;
import cn.yx.common.core.enums.io.FileOrderByEnum;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.util.RandomUtil;
import cn.yx.common.core.util.date.DateFormatUtil;
import cn.yx.common.core.util.date.DateUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Objects;

/**
 * <p>文件工具类，如获取WEB-INF目录根路径，获取classpath根路径，文件后缀判断等</p>
 *
 * @author Wgssmart
 */
@Slf4j
public final class FileUtil {

    /**
     * 文件名随机数的位数
     */
    public static final int FILE_NAME_RANDOM_DIGIT = 7;

    private FileUtil() {
    }

    /**
     * 获取classpath根路径
     *
     * @return classpath根路径
     */
    public static String getClasspath() {
        URL url = FileUtil.class.getResource(FileConstant.ROOT_PATH);
        if (Objects.isNull(url)) {
            log.error("get url from root path: {} error", FileConstant.ROOT_PATH);
            throw new CommonException(FileUtilMessageKey.GET_ROOT_PATH_ERROR);
        }
        return url.getPath();
    }

    /**
     * 获取WEB-INF根路径
     *
     * @return WEB-INF根路径
     */
    public static String getWebInfPath() {
        String classPath = getClasspath();
        return classPath.substring(0, classPath.length() - (FileConstant.CLASSES.length() + 1));
    }

    /**
     * <p>通过指定的配置文件路径读取配置文件，可按两种形式指定：</p>
     * <p>第一种形式为classpath:/autoload.properties</p>
     * <p>第二种形式为/WEB-INF/config/autoload.properties</p>
     *
     * @param configLocation 配置文件的存放位置
     * @return 配置文件的完整路径
     */
    public static String getResourcePath(String configLocation) {
        if (configLocation.contains(FileConstant.CLASSPATH)) {
            return getClasspath() + configLocation.substring(FileConstant.CLASSPATH.length());
        }
        if (configLocation.contains(FileConstant.WEB_INF)) {
            return getWebInfPath() + configLocation.substring(FileConstant.WEB_INF.length());
        }
        return configLocation;
    }

    /**
     * 获取指定文件名的后缀，包括.符号
     *
     * @param fileName 完整的文件名称
     * @return 文件后缀，包括.符号，如.png
     */
    public static String getFullExt(String fileName) {
        return fileName.substring(fileName.lastIndexOf(SeparatorConstant.DOT)).toLowerCase();
    }

    /**
     * 获取指定文件名的后缀，不包括.符号
     *
     * @param filename 完事的文件名
     * @return 文件后缀，不包括.符号，如png
     */
    public static String getExt(String filename) {
        return filename.substring(filename.lastIndexOf(SeparatorConstant.DOT) + 1).toLowerCase();
    }

    /**
     * 判断给定的文件名是否符合指定的文件后缀
     *
     * @param fileName   完整的文件名
     * @param extensions 需要比对文的件后缀，文件后缀包括.符号，多个文件后缀用英文半角,隔开
     * @return 如果文件名符合给定的后缀，则返回true，否则返回false
     */
    public static boolean checkExt(String fileName, String extensions) {
        String[] extArray = extensions.toLowerCase().split(SeparatorConstant.COMMA);
        String fileNameLowerCase = fileName.toLowerCase();
        for (String ext : extArray) {
            if (fileNameLowerCase.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取指定文件名的不包括后缀部分的文件名，只一个.后缀的情况
     *
     * @param filename 指定的文件名
     * @return 去除后缀的文件名
     */
    public static String getFileNameWithoutExt(String filename) {
        return filename.substring(0, filename.lastIndexOf(SeparatorConstant.DOT));
    }

    /**
     * 获取指定文件名的不包括后缀部分的文件名，后缀可指定
     *
     * @param filename  指定的文件名
     * @param extension 指定的后缀
     * @return 去除后缀的文件名
     */
    public static String getFileNameWithoutExt(String filename, String extension) {
        return filename.substring(0, filename.toLowerCase().lastIndexOf(extension.toLowerCase()));
    }

    /**
     * 通过比较文件的最后修改时间来判断一个文件是否比另外一个文件更新
     *
     * @param filename      判断是否为新文件的文件路径
     * @param otherFilename 用来比较的文件的路径
     * @return 如果用来判断是否为新文件的文件对应的最后修改时间比用于比较的文件的最后修改时间大，则返回true，否则返回false
     */
    public static boolean isNew(String filename, String otherFilename) {
        return isNew(new File(filename), new File(otherFilename));
    }

    /**
     * 通过比较文件的最后修改时间来判断一个文件是否比另外一个文件更新
     *
     * @param file      判断是否为新文件的文件对象
     * @param otherFile 用来比较的文件对象
     * @return 如果用来判断是否为新文件的文件对应的最后修改时间比用于比较的文件的最后修改时间大，则返回true，否则返回false
     */
    public static boolean isNew(File file, File otherFile) {
        return file.lastModified() > otherFile.lastModified();
    }

    /**
     * 基于时间格式获取新文件名，包括文件扩展名。文件名以6位随机数结尾
     *
     * @param fileName 旧文件名
     * @return 包含扩展名的新文件名
     */
    public static String newFileName(String fileName) {
        return newFileName(fileName, FILE_NAME_RANDOM_DIGIT);
    }

    /**
     * 基于时间格式获取新文件名，包括文件扩展名。可指定文件名结尾的随机数范围
     *
     * @param fileName 旧文件名
     * @param seqDigit 文件名末尾随机数位数
     * @return 包含扩展名的新文件名
     */
    public static String newFileName(String fileName, int seqDigit) {
        return DateFormatUtil.format(DateUtil.millis(), DatePatternEnum.DATETIME_SIMPLE.getValue())
                + RandomUtil.randomIntStr(seqDigit) + getFullExt(fileName).toLowerCase();
    }

    /**
     * 基于时间格式获取新文件名，但不包括文件扩展名。文件名以6位随机数结尾
     *
     * @return 不包含扩展名的新文件名
     */
    public static String newFileNameWithoutExt() {
        return newFileNameWithoutExt(FILE_NAME_RANDOM_DIGIT);
    }

    /**
     * 基于时间格式获取新文件名，但不包括文件扩展名。可指定文件名结尾的随机数位数
     *
     * @param seqDigit 文件名末尾随机数位数
     * @return 不包含扩展名的新文件名
     */
    public static String newFileNameWithoutExt(int seqDigit) {
        return DateFormatUtil.format(DateUtil.millis(), DatePatternEnum.DATETIME_SIMPLE.getValue())
                + RandomUtil.randomIntStr(seqDigit);
    }

    /**
     * 判断指定目录中是否存在指定名称的文件或目录
     *
     * @param dir  指定的目录
     * @param name 文件名
     * @return 如果存在返回true，否则返回false
     */
    public static boolean exist(String dir, String name) {
        return new File(dir, name).exists();
    }

    /**
     * 获取指定路径的文件的目录部分
     *
     * @param filePath 文件路径
     * @return 指定文件所在目录的完整路径
     */
    public static String getDirs(String filePath) {
        return filePath.substring(0, filePath.lastIndexOf(File.separator));
    }

    /**
     * 获取指定路径的文件的文件名
     *
     * @param filePath 文件路径
     * @return 仅文件名部分，不包含目录
     */
    public static String getFilename(String filePath) {
        return filePath.substring(filePath.lastIndexOf(File.separator) + 1);
    }

    /**
     * 在指定的父目录中创建子目录
     *
     * @param parent 父目录路径
     * @param dirs   子目录，可以是多级子目录
     * @return 创建的子目录的绝对路径
     */
    public static String mkdirs(String parent, String dirs) {
        File path = new File(parent, dirs);
        if (path.exists()) {
            return path.getAbsolutePath();
        }
        if (!path.mkdirs()) {
            log.error("mkdirs error, parent: {}, dirs: {}", parent, dirs);
            // @SimplifiedChinese=创建目录异常
            // @English=Make dirs error
            throw new CommonException(FileUtilMessageKey.MKDIRS_ERROR);
        }
        return path.getAbsolutePath();
    }

    /**
     * 在指定的父目录中创建子目录
     *
     * @param parent 父目录文件
     * @param dirs   子目录，可以是多级子目录
     * @return 创建的子目录的绝对路径
     */
    public static String mkdirs(File parent, String dirs) {
        File path = new File(parent, dirs);
        if (path.exists()) {
            return path.getAbsolutePath();
        }
        if (!path.mkdirs()) {
            log.error("mkdirs error, parent: {}, dirs: {}", parent.getAbsolutePath(), dirs);
            throw new CommonException(FileUtilMessageKey.MKDIRS_ERROR);
        }
        return path.getAbsolutePath();
    }

    /**
     * 根据指定的路径，如果没有此路径则创建并返回绝对路径，否则直接返回绝对路径
     *
     * @param dirs 多级目录
     * @return 创建的目录的完整路径
     */
    public static String mkdirs(String dirs) {
        File path = new File(dirs);
        if (path.exists()) {
            return path.getAbsolutePath();
        }
        if (!path.mkdirs()) {
            log.error("mkdirs error, dirs: {}", dirs);
            throw new CommonException(FileUtilMessageKey.MKDIRS_ERROR);
        }
        return path.getAbsolutePath();
    }

    /**
     * 根据完整的路径创建新文件，不创建不存在的目录
     *
     * @param filePath 文件路径
     */
    public static void createNewFile(String filePath) {
        createNewFile(filePath, false);
    }

    /**
     * 根据完整的路径创建新文件，可指定是否创建不存在的目录
     *
     * @param filePath 文件路径
     * @param mkDirs   是否创建不存在的目录
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void createNewFile(String filePath, boolean mkDirs) {
        File file = new File(filePath);
        if (file.exists()) {
            return;
        }
        if (mkDirs) {
            String dirs = getDirs(filePath);
            mkdirs(dirs);
        }
        try {
            file.createNewFile();
        } catch (IOException e) {
            log.error("create new file error: {}, file path: {}", e.getMessage(), filePath);
            throw new CommonException(FileUtilMessageKey.CREATE_NEW_FILE_ERROR, e);
        }
    }

    /**
     * 拷贝文件，支持任意文件类型
     *
     * @param srcPath  源文件路径
     * @param destPath 目标文件路径
     */
    public static void copyFile(String srcPath, String destPath) {
        createNewFile(destPath);
        try (InputStream in = new FileInputStream(srcPath);
             FileOutputStream out = new FileOutputStream(destPath)) {
            byte[] bytes = new byte[FileConstant.DEFAULT_BUFFER_SIZE];
            int len;
            while ((len = in.read(bytes)) != -1) {
                out.write(bytes, 0, len);
            }
        } catch (IOException e) {
            log.error("copy file error: {}, src path: {}, dest path: {}", e.getMessage(), srcPath, destPath);
            throw new CommonException(FileUtilMessageKey.COPY_FILE_ERROR, e);
        }
    }

    /**
     * 拷贝文件，支持文本文件类型
     *
     * @param srcPath  源文件路径
     * @param destPath 目标文件路径
     */
    public static void copyTextFile(String srcPath, String destPath) {
        createNewFile(destPath);
        try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(srcPath));
             BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(
                     new FileOutputStream(destPath), inputStreamReader.getEncoding()))) {
            String line;
            while (Objects.nonNull(line = bufferedReader.readLine())) {
                bufferedWriter.write(line + "\n");
            }
        } catch (IOException e) {
            log.error("copy text file error: {}, src path: {}, dest path: {}", e.getMessage(), srcPath, destPath);
            throw new CommonException(FileUtilMessageKey.COPY_TEXT_FILE_ERROR, e);
        }
    }

    /**
     * 删除指定路径的文件
     *
     * @param path 文件路径
     * @return 指定文件是否删除成功
     */
    public static boolean deleteFile(String path) {
        File file = new File(path);
        return file.delete();
    }

    /**
     * 删除指定的目录，该目录下的所有文件和目录也会删除
     *
     * @param srcDir 需要删除的目录
     * @return 指定目录是否全部删除成功，若该目录中有至少一个文件未删除则返回false
     */
    public static boolean deleteFiles(String srcDir) {
        File file = new File(srcDir);
        if (!file.isDirectory()) {
            // 如果是文件，直接删除
            return file.delete();
        }
        File[] files = file.listFiles();
        if (ArrayUtil.isEmpty(files)) {
            // 如果是空目录，直接删除
            return file.delete();
        }
        boolean deleted = true;
        for (File f : files) {
            if (f.isDirectory()) {
                // 如果是目录，调用deleteFiles递归删除
                deleted = deleted && deleteFiles(f.getAbsolutePath());
            } else {
                // 如果是文件，直接删除
                deleted = deleted && f.delete();
            }
        }
        return deleted && file.delete();
    }

    /**
     * 获取文件的基础属性
     *
     * @param file 文件对象
     * @return 文件属性
     */
    public static BasicFileAttributes attributes(File file) {
        try {
            return Files.readAttributes(Paths.get(file.toURI()), BasicFileAttributes.class);
        } catch (IOException e) {
            log.error("read file attributes error: {}, file path: {}", e.getMessage(), file.getAbsolutePath());
            throw new CommonException(FileUtilMessageKey.READ_FILE_ATTRIBUTES_ERROR, e);
        }
    }

    /**
     * 获取文件的基础属性
     *
     * @param filePath 文件路径
     * @return 文件属性
     */
    public static BasicFileAttributes attributes(String filePath) {
        try {
            return Files.readAttributes(Paths.get(filePath), BasicFileAttributes.class);
        } catch (IOException e) {
            log.error("read file attributes error: {}, file path: {}", e.getMessage(), filePath);
            throw new CommonException(FileUtilMessageKey.READ_FILE_ATTRIBUTES_ERROR, e);
        }
    }

    /**
     * 返回指定目录中的所有文件，可指定按修改时间降序或升序排列
     *
     * @param dir             目录路径
     * @param orderByTypeEnum 按文件修改时间的排序方式
     * @return 目录下的文件列表
     */
    public static File[] listFilesOrderByModified(String dir, OrderByTypeEnum orderByTypeEnum) {
        return listFilesOrderBy(dir, FileOrderByEnum.MODIFIED, orderByTypeEnum);
    }

    /**
     * 返回指定目录中的所有文件，可指定按文件名、文件创建时间、文件修改时间或文件大小降序或升序排列。可保证文件排序的准确性，但不保证目录排序的准确性
     *
     * @param dir             目录路径
     * @param fileOrderByEnum 排序字段
     * @param orderByTypeEnum 排序方式
     * @return 目录下的文件列表
     */
    public static File[] listFilesOrderBy(String dir, FileOrderByEnum fileOrderByEnum, OrderByTypeEnum orderByTypeEnum) {
        File file = new File(dir);
        if (file.exists() && file.isDirectory()) {
            File[] files = file.listFiles();
            if (ArrayUtil.isNotEmpty(files)) {
                sortFiles(files, fileOrderByEnum, orderByTypeEnum);
            }
            return files;
        }
        return null;
    }

    /**
     * 文件数组，可指定按文件名、文件创建时间、文件修改时间或文件大小降序或升序排列。可保证文件排序的准确性，但不保证目录排序的准确性
     *
     * @param files           文件对象数组
     * @param fileOrderByEnum 排序字段
     * @param orderByTypeEnum 排序方式
     */
    public static void sortFiles(File[] files, FileOrderByEnum fileOrderByEnum, OrderByTypeEnum orderByTypeEnum) {
        Arrays.sort(files, (file1, file2) -> {
            int compareResult = compareResult(file1, file2, fileOrderByEnum);
            // compareResult = -1，则升序
            return OrderByTypeEnum.ASC.equals(orderByTypeEnum) ? compareResult : -compareResult;
        });
    }

    /**
     * 根据排序类型获取两个文件的比较结果
     *
     * @param file1           文件1
     * @param file2           文件2
     * @param fileOrderByEnum 排序类型
     * @return 比较结果，-1，0或1
     */
    private static int compareResult(File file1, File file2, FileOrderByEnum fileOrderByEnum) {
        int compareResult = 0;
        if (fileOrderByEnum == FileOrderByEnum.NAME) {
            compareResult = file1.getName().compareTo(file2.getName());
        }
        if (fileOrderByEnum == FileOrderByEnum.MODIFIED) {
            long timeDiff = file1.lastModified() - file2.lastModified();
            compareResult = getCompareResult(timeDiff);
        }
        if (fileOrderByEnum == FileOrderByEnum.CREATED) {
            BasicFileAttributes attributes = attributes(file1);
            BasicFileAttributes otherAttributes = attributes(file2);
            long timeDiff = attributes.creationTime().toMillis() - otherAttributes.creationTime().toMillis();
            compareResult = getCompareResult(timeDiff);
        }
        if (fileOrderByEnum == FileOrderByEnum.SIZE) {
            BasicFileAttributes attributes = attributes(file1);
            BasicFileAttributes otherAttributes = attributes(file2);
            long sizeDiff = attributes.size() - otherAttributes.size();
            compareResult = getCompareResult(sizeDiff);
        }
        return compareResult;
    }

    /**
     * 通过差值获取比较的结果，差值小于0，则比较结果为-1，差值等于0，则比较结果为0，差值大于0，则比较结果为1
     *
     * @param diff 差值
     * @return 比较结果，-1，0或1
     */
    private static int getCompareResult(long diff) {
        if (diff < 0) {
            return -1;
        }
        return diff == 0 ? 0 : 1;
    }

}
